package poset;
import graphs.CycleException;

import java.util.ArrayList;
import java.util.TreeMap;
import java.util.TreeSet;


import spaces.ChargeableNode;
import spaces.Node;
import spaces.NodeSpace;
import vectors.Vector;

public class PosetSpace{
	
	private static final double FIX_FACTOR = 0.01;
	private final String relation;
	private final int dims;
	private ArrayList<User> users;
	private NodeSpace nodeSpace;
	private boolean isInitialized = false;
	private double buffer = 1;
	private double tension;
	
	public PosetSpace(String relation, int dims){
		this(relation, new ArrayList<User>(), dims);
	}
	
	public PosetSpace(String relation, ArrayList<User> users, int dims){
		this.relation = relation;
		this.users = users;
		this.dims = dims;
	}
	
	public void initialize(){
		TreeSet<Node> nodes = new TreeSet<Node>();
		nodes.addAll(users);
		for(User user : users){
			for(Ordering ordering : user.getPoset(relation).getOrderings()){
				nodes.add(ordering.getItem1());
				nodes.add(ordering.getItem2());
			}
		}
		nodeSpace = new NodeSpace(dims, nodes);
		for(Node node : nodeSpace.getNodes()){
			node.setPos(Vector.random(dims).times(buffer));
		}
		isInitialized = true;
	}
	
	public void next(){
		if(!isInitialized){
			throw new IllegalStateException("Cannot call next until initialized.");
		}
		tension = 0;
		for(User user : users){
			TreeMap<Item,Vector> diffs = getDifferences(user);
			ArrayList<Double> dists = new ArrayList<Double>();
			ArrayList<Item> items = new ArrayList<Item>();
			for(Item item : diffs.keySet()){
				items.add(item);
				dists.add(diffs.get(item).getLengthSq());
			}
			Poset realRelOrder;
			realRelOrder = createPosetFromDistances(dists, items);
			ArrayList<Ordering> wrongOrders = user.getPoset(relation).getUnsatisfiedExplicitOrderings(realRelOrder);
			for(Ordering wo : wrongOrders){
				
				// Find amount incorrect
				Vector toItem1 = diffs.get(wo.getItem1());
				Vector toItem2 = diffs.get(wo.getItem2());
				double error = toItem1.getLength() - toItem2.getLength() + buffer;
				tension+=error;
				
				// Add charges to correct
				Vector error1 = toItem1.normalize().times(error*FIX_FACTOR);
				wo.getItem1().addCharge(error1.times(-1));
				user.addCharge(error1);
				
				Vector error2 = toItem2.normalize().times(error*FIX_FACTOR);
				wo.getItem2().addCharge(error2);
				user.addCharge(error2.times(-1));
			}
			//Apply the charges
			for(Node node : nodeSpace.getNodes()){
				((ChargeableNode)node).applyCharge();
			}
		}
	}

	//TODO: Make faster by using a safe insert and not inserting Implicitly Redundant orderings somehow
	private Poset createPosetFromDistances(ArrayList<Double> dists, ArrayList<Item> items){
		Poset poset = new Poset();
		for(int i=0;i<dists.size();i++){
			for(int j=0;j<dists.size();j++){
				if(dists.get(j) - dists.get(i) > buffer){
					try {
						poset.addOrdering(new Ordering(items.get(i), items.get(j)));
					} catch (CycleException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (ExplicitRedundancyException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (ReflexiveOrderingException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
		return poset;
	}

	private TreeMap<Item,Vector> getDifferences(User user) {
		TreeMap<Item,Vector> diffs = new TreeMap<Item,Vector>();
		for(Item item : user.getPoset(relation).getItems()){
			diffs.put(item, item.getPos().minus(user.getPos()));
		}
		return diffs;
	}

	public String getRelation(){
		return relation;
	}

	public int getDims() {
		return dims;
	}

	public double getMin(int dim) {
		if(!isInitialized){
			throw new IllegalStateException("Cannot call getMin until initialized.");
		}
		double min = Double.MAX_VALUE;
		double curVal;
		for(Node node : nodeSpace.getNodes()){
			curVal = node.getPos().getValue(dim);
			min = Math.min(min, curVal);
		}
		return min;
	}
	
	public double getMax(int dim) {
		if(!isInitialized){
			throw new IllegalStateException("Cannot call getMax until initialized.");
		}
		double max = Double.MIN_VALUE;
		double curVal;
		for(Node node : nodeSpace.getNodes()){
			curVal = node.getPos().getValue(dim);
			max = Math.max(max, curVal);
		}
		return max;
	}

	public NodeSpace getNodeSpace() {
		if(!isInitialized){
			throw new IllegalStateException("Cannot call getNodeSpace until initialized.");
		}
		return nodeSpace;
	}

	public double getTension() {
		return tension;
	}
}
